﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Soundteller.Audio.Model;
using Soundteller.Audio.Repositories.Interfaces;
using Soundteller.Audio.Services.Interfaces;
using Soundteller.Bus.Services.Interfaces;

namespace Soundteller.Audio.Services
{
    public class Metronome : IMetronome
    {
        private readonly IBeatCalculator _beatCalculator;
        private readonly ISongRepository _songRepository;
        private readonly IBeatRepository _beatRepository;
        private readonly ISoundBufferRepository _soundBufferRepository;
        private readonly IPublisher _publisher;

        public Metronome(IBeatCalculator beatCalculator, ISongRepository songTimeRepository,
            IBeatRepository beatRepository, ISoundBufferRepository soundBufferRepository, IPublisher publisher)
        {
            if (beatCalculator == null) throw new ArgumentNullException("beatCalculator");
            if (songTimeRepository == null) throw new ArgumentNullException("songTimeRepository");
            if (beatRepository == null) throw new ArgumentNullException("beatRepository");
            if (soundBufferRepository == null) throw new ArgumentNullException("soundBufferRepository");
            if (publisher == null) throw new ArgumentNullException("publisher");

            _beatCalculator = beatCalculator;
            _songRepository = songTimeRepository;
            _beatRepository = beatRepository;
            _soundBufferRepository = soundBufferRepository;
            _publisher = publisher;
        }

        public void Tick(TimeSpan elapsedTime)
        {
            Debug.WriteLine("{0}.Tick( {1} )", GetType().Namespace, elapsedTime);

            var song = _songRepository.GetActiveSong();
            if (song == null) 
                return;

            UpdateSongTime(elapsedTime, song);
            var updatedBeat = UpdateBeat(song);

            var sounds = GetUnbufferedSoundsToBeat(song, updatedBeat);
            foreach (var sound in sounds)
            {
                _soundBufferRepository.Push(sound);
                sound.IsBuffered = true;
            }

            if (song.LoopEnabled && updatedBeat >= song.EndBeat)
                ResetSong(song);
        }

        private void UpdateSongTime(TimeSpan elapsedTime, Song song)
        {
            song.Time = song.Time.Add(elapsedTime);
            _songRepository.Update(song);
        }

        private double UpdateBeat(Song song)
        {
            var currentBeat = _beatCalculator.Calculate(song.Time, song.Bpm);

            _beatRepository.Update(currentBeat);

            return currentBeat;
        }

        public List<Sound> GetUnbufferedSoundsToBeat(Song song, double toBeat)
        {
            return (from sound in song.Sounds
                    where sound.IsBuffered == false
                        && sound.Beat <= toBeat
                    select sound).ToList();
        }

        private void ResetSong(Song song)
        {
            song.Time = new TimeSpan(0, 0, 0);
            song.Sounds.ForEach(s => s.IsBuffered = false);

            _songRepository.Update(song);
            _beatRepository.Update(0);

            _publisher.Publish(new SongResetMessage());
        }
    }
}